package anasazi;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import repast.simphony.engine.schedule.ScheduledMethod;
import repast.simphony.query.space.grid.GridCell;
import repast.simphony.query.space.grid.GridCellNgh;
import repast.simphony.space.continuous.ContinuousSpace;
import repast.simphony.space.continuous.NdPoint;
import repast.simphony.space.grid.CellAccessor;
import repast.simphony.space.grid.Grid;
import repast.simphony.space.grid.GridDimensions;
import repast.simphony.space.grid.GridPoint;
import repast.simphony.space.grid.GridPointTranslator;

public class Household {
	
	private ContinuousSpace<Object> space;
	private Grid<Object> grid;
	
	
	//VARIABLES (Modified through the graphic interface). 
	
	private static final double harvestAdjustment = 0.54; 
	private static final double harvestVariance = 0.40;
	
	//Not static, because they are redefined in the example. 
	private int deathAge = 38;
	private int fertilityEndsAge = 34;
	
	private static double fertility = 0.155; 
	 
	//ENVIRONMENTAL VARIABLES (Defined in the code). 
	private static int typicalHouseholdSize = 5; //a household consist of 5 persons 
	
	private static int baseNutritionNeed = 160;  //a person needs 160 kg of food (corn) each year
	private static final int HouseholdMinNutritionNeed = (baseNutritionNeed * typicalHouseholdSize);
	private static final int HouseholdMaxNutritionNeed = (baseNutritionNeed * typicalHouseholdSize);  
	
	private static final int minFertilityAge = 16; //when agents can start to reproduce. An agent represent a household, and reproduction means here that a daughter leaves the household to start a new household
	private static final int maxFertilityAge = 16;
	  
	private static final int minDeathAge = 30; // maximum age of household
	private static final int maxDeathAge = 36;
	
	private static final int minFertilityEndsAge = 30; //agent stop reproducing at FertilityEndAge
	private static final int maxFertilityEndsAge = 32;
	
	private static final double maxFertility = 0.125; //probability that a fertile agent reproduces
	private static final double minFertility = 0.125;
	
	private static final double maizeGiftToChild = 0.33 ; //a new household get part of the storage of the parent household
	private static final double waterSourceDistance = 16.0 ; //agents like to live within a certain distance of the water
	private static final int yearsOfStock =2; // number of years corn can be stored.

	private static final int householdMinInitialAge = 0; //to determine ages of initial households 
  	private static final int householdMaxInitialAge = 29;
  	private static final int householdMinInitialCorn = 2000; //to determine storage of initial households
  	private static final int householdMaxInitialCorn = 2400;
	
	
	private static final int numFarms = 0;
	private static final int hoseholdMinNutritionNeed = 0;
	
		
	private double agedCornStocks[];
	
	//Dynamic, initialized by the constructor. 
	private int age; 
	private double nutritionNeed;		
	private double nutritionNeedRemaining;
	private int fertilityAge; 
	 	 	
	private double lastHarvest;			  
	
	
	//Potential Farms
	private Farm[] potFarms; 
	
	//Occupied households
	public int ocHousehold;
	
	private Farm farmPlot; 
	
	//Household location
	private NdPoint location; 
	

	public Household(ContinuousSpace<Object> space, Grid<Object> grid) {
		this.space = space;
		this.grid = grid;

		this.agedCornStocks = new double[3];		
				
		this.agedCornStocks[0] = (householdMinInitialCorn + (int)(Math.random() * ((householdMaxInitialCorn - householdMinInitialCorn) + 1)));  
		this.agedCornStocks[1] = (householdMinInitialCorn + (int)(Math.random() * ((householdMaxInitialCorn - householdMinInitialCorn) + 1)));		 
		this.agedCornStocks[2] = (householdMinInitialCorn + (int)(Math.random() * ((householdMaxInitialCorn - householdMinInitialCorn) + 1)));
		
		this.age = householdMinInitialAge + (int)(Math.random() * ((householdMaxInitialAge - householdMinInitialAge) + 1));
		
		this.nutritionNeed = HouseholdMinNutritionNeed + (int)(Math.random() * ((HouseholdMaxNutritionNeed - HouseholdMinNutritionNeed) + 1)); 	
		this.fertilityAge = minFertilityAge + (int)(Math.random() * ((maxFertilityAge - minFertilityAge) + 1));
		this.deathAge = minDeathAge + (int)(Math.random() * ((maxDeathAge - minDeathAge) + 1));
		this.fertilityEndsAge = minFertilityEndsAge + (int)(Math.random() * ((maxFertilityEndsAge - minFertilityEndsAge) + 1));
		
		this.fertility = minFertility + (float)(Math.random() * ((maxFertility - minFertility) + 1));
		this.lastHarvest = 0; 
		
		this.ocHousehold = 0; 
		this.findFarmAndSettlement();
	}
	
	public double eucDistance(NdPoint location1, NdPoint location2){
		return Math.sqrt(Math.pow((location1.getX() - location2.getX()), 2) + Math.pow((location1.getY() - location2.getY()), 2));	
	}
	
	public double eucDistance(GridPoint location1, GridPoint location2){
		return Math.sqrt(Math.pow((location1.getX() - location2.getX()), 2) + Math.pow((location1.getY() - location2.getY()), 2));	
	}
	
	@ScheduledMethod(start = 1, interval = 1)
	public void go(){
		this.harvestConsumption(); 
		this.death(); 
		
		//agents who expect not to have sufficient food next timestep move to a new spot (if available). If no spots are available, they leave the system.
		double harvestEstimation = this.estimateHarvest();  
		if(harvestEstimation < nutritionNeed){
			//we have to check everytime whether locations are available for moving agents. Could be implemented more efficiently by only updating selected info. 
			this.determinePotentialFarms();			
			
			try{
				//this.getPatch(this.location).ocfarm = 0;
				Map map = this.getMap(this.location);
				
			} catch (Exception ex){
				
			}
			
			this.findFarmAndSettlement();
		}
		this.determinePotentialFarms();
		if(this.age > fertilityAge && age <= fertilityEndsAge && Math.random() < fertility){
			if(this.potFarms.length > 0){
				this.determinePotentialFarms(); 
				this.fissioning(); 
			}
		}
	}
	
	public Map getMap(NdPoint location) throws Exception{
		 
		Iterator<Object> it = this.space.getObjectsAt(location.getX(), location.getY()).iterator();
		while(it.hasNext()){
			Object obj = it.next();
			
			if (this.isMap(obj)){
				return (Map)obj; 
			}
		}
		throw new Exception();
	}
	
	private boolean isMap(Object obj){		
		return obj.equals(mapEmpty.class) 
				|| obj.equals(mapGeneral.class)
				|| obj.equals(mapKinbiko.class) 
				|| obj.equals(mapMid.class)
				|| obj.equals(mapMidDunes.class)
				|| obj.equals(mapNatural.class) || obj.equals(mapNorth.class)
				|| obj.equals(mapNorthDunes.class)
				|| obj.equals(mapUplands.class);
	}
	
	public double estimateHarvest(){
		double total = 0; 
		int ys = yearsOfStock -1; 
		
		while(ys > -1){
			total = total + this.agedCornStocks[ys];
			ys--;
		}
		return total + this.lastHarvest; 
	}
	
	//determine the list of potential locations for a farm to move to. A potential location to farm is a place where not somebody is farming and where the baseyield
	//is higher than the minimum amount of food needed and where nobody has build a settlement
	private void determinePotentialFarms(){
		
		this.potFarms = new Farm[numFarms];	
		
		int index = 0; 
		//Iterator it = this.grid.getObjects().iterator();
		for(Iterator<Object> it = this.grid.getObjects().iterator(); it.hasNext(); ){
			Object obj = it.next(); 
			if(obj.getClass().equals(Farm.class)){
				Farm candidate = (Farm)obj;
				if(candidate.baseYield > hoseholdMinNutritionNeed && candidate.ocfarm == 0){
					this.potFarms[index] = candidate; 					
				}
			} 
		}										 
		
	}
	
	// COMPLETED
	private Farm determineBestFarm(){				
		
		double distancetns = 1000;
		int bestFarm = -1; 
		double dist; 
		
		NdPoint pt = space.getLocation(this);
		
		for(int i = 0; i<potFarms.length; i++){
			dist = this.eucDistance(pt, potFarms[i].getLocation());
			if(dist < distancetns){
				bestFarm = i;
				distancetns = dist;				
			}
		}
		 
		if(potFarms.length > 0){
			//if length potfarms > 0 [set potfarms remove bestfarm potfarms] (for some reason, it's removed).
			return this.potFarms[bestFarm];
		} else {
			return null; 
		}
	}
	
	// COMPLETED
	private void findFarmAndSettlement(){
		//find a new spot for the settlement (might remain the same location as before)				 
		boolean bool = true; 
		
		//coordinates of the household location. 
				
		float[] potFarms = new float[4];
		
		if(potFarms.length > 0){
			
			Farm bestFarm = this.determineBestFarm();
			int by = bestFarm.getBaseYield();
			//GridPoint loc = bestFarm.getLocation();
			
			this.farmPlot = bestFarm;
			
			
			//Mark as occupied by a farm the patch of the farmPlot. 
			GridPoint householdLocation = this.grid.getLocation(this);
			Iterator<Object> it = this.grid.getObjectsAt(householdLocation.getX(), householdLocation.getY()).iterator();
						
			while(it.hasNext()){
				Object obj = it.next();
				if(this.isMap(obj)){				
					Map map = (Map)obj; 
					map.ocfarm = 1; 
					break;
				}
			}		
			

			//if there are cells with water which are not farmed and in a zone that is less productive than the zone where the favorite farm plot is located						
			
			List<Map> patchList = new ArrayList<Map>();
			
			for( it = this.grid.getObjects().iterator(); it.hasNext(); ){
			//while(it.hasNext() && !condition){			
				Object obj = it.next(); 
				if(this.isMap(obj)){
					Map map = (Map)obj;
					if(map.watersource == 1 && map.ocfarm == 0 && map.baseYield < by){
						patchList.add(map);												 
					}									
				}			
			}
			
			
			if(patchList.size() > 0){ //getPatches((watersource == 1 && ocfarm == 0) && yield < by)
				//find the most nearby spot
				
				double minDistance = Double.MAX_VALUE;
				Map closestPatch = null; 
				
				for (Iterator<Map> patchIt = patchList.iterator(); it.hasNext(); ){
					Map map = patchIt.next();
					 					
					if(map.waterDistance < waterSourceDistance && this.eucDistance(map.getLocation(), bestFarm.getLocation()) < minDistance){
						minDistance = this.eucDistance(map.getLocation(), bestFarm.getLocation());
						closestPatch = map; 
					}
				}
				
				if(minDistance < Double.MAX_VALUE){
					//A Patch has been found
					if(this.eucDistance(closestPatch.getLocation(), bestFarm.getLocation())< waterSourceDistance){												
						this.location = closestPatch.getLocation(); 
						bool = false; 
					}
				}
				
				if(!bool){
					//If the favorite location is nearby move to that spot.
					
					//min-one-of patches [ocfarm = 0 and hydro <= 0][distance xh yh]
					minDistance = Double.MAX_VALUE; 
					closestPatch = null;
					
					for (Iterator<Map> patchIt = patchList.iterator(); it.hasNext(); ){
						Map patch = patchIt.next();
				 							
						if(patch.ocfarm == 0 && patch.hydro <= 0 && (this.eucDistance(patch.getLocation(), this.location) < minDistance)){							
							minDistance = this.eucDistance(patch.getLocation(), this.location);
							closestPatch = patch; 
						}
					}	
					
					this.location = closestPatch.getLocation(); 
					closestPatch.ochousehold++;  					
				} 
				
				if(bool == true){ //If no settlement is found yet. 
					//Find a location that is not farmed with nearby water (but this might be in the same zone as the farm plot). 
					
					minDistance = Double.MAX_VALUE;
					closestPatch = null;
					
					for (Iterator<Map> patchIt = patchList.iterator(); it.hasNext(); ){
						Map patch = patchIt.next();
				 							
						if(patch.ocfarm == 0 && (this.eucDistance(patch.getLocation(), bestFarm.getLocation()) < minDistance)){							
							minDistance = this.eucDistance(patch.getLocation(), bestFarm.getLocation());
							closestPatch = patch; 
						}
					}	
					
					if(this.eucDistance(closestPatch.getLocation(), bestFarm.getLocation()) <=  waterSourceDistance){
						this.location = closestPatch.getLocation(); 
						bool = false; 						
					}
					if(bool == false){
						
						minDistance = Double.MAX_VALUE;
						closestPatch = null;
						
						for (Iterator<Map> patchIt = patchList.iterator(); it.hasNext(); ){
							Map patch = patchIt.next();
					 							
							if(patch.ocfarm == 0 && patch.hydro <= 0 && (this.eucDistance(patch.getLocation(), this.location) < minDistance)){							
								minDistance = this.eucDistance(patch.getLocation(), bestFarm.getLocation());
								closestPatch = patch; 
							}
						}
						
						this.location = closestPatch.getLocation(); 
						closestPatch.ochousehold++; 						
					}					
				}
				
				if(bool == true){ //If still no settlement is found, try to find a location that is not farmed even if this is not close to water. 
					
					minDistance = Double.MAX_VALUE;
					closestPatch = null;
					
					for (Iterator<Map> patchIt = patchList.iterator(); it.hasNext(); ){
						Map patch = patchIt.next();
				 							
						if(patch.ocfarm == 0 && (this.eucDistance(patch.getLocation(), bestFarm.getLocation()) < minDistance)){							
							minDistance = this.eucDistance(patch.getLocation(), bestFarm.getLocation());
							closestPatch = patch; 
						}
					}
					if(minDistance < Double.MAX_VALUE){
						this.location = closestPatch.getLocation(); 
						bool = false;
					}
					
					if(bool == false){
						minDistance = Double.MAX_VALUE;
						closestPatch = null;
						
						for (Iterator<Map> patchIt = patchList.iterator(); it.hasNext(); ){
							Map patch = patchIt.next();
					 							
							if(patch.ocfarm == 0 && patch.hydro <= 0 && (this.eucDistance(patch.getLocation(), this.location) < minDistance)){							
								minDistance = this.eucDistance(patch.getLocation(), bestFarm.getLocation());
								closestPatch = patch; 
							}
						}	
					}
					this.location = closestPatch.getLocation(); 
					closestPatch.ochousehold++; 
				}
				
				if(bool == true){//if no possible settlement is found, leave the system.
					this.farmPlot.ocfarm = 0; 
					//FIXME kill the process. 
					//die() 
				} 												
			}
		}
	}

	//TO BE tested: a new household is created, verify if it's actually created. 
	// creates a new agent and update relevant info from new and parent agent. Agent will farm at a nearby available location
	private void fissioning(){
		
		int ys = yearsOfStock; 
		while(ys > -1){
			this.agedCornStocks[ys] = (1 - maizeGiftToChild) * this.agedCornStocks[ys];
			ys--;
		}
		Household hh = new Household(space, grid);
		hh.age = 0;  //override the value derived in inithouseld, since this will be a fresh household. 
		
		ys = yearsOfStock;
		while(ys > -1){
			hh.agedCornStocks[ys] = (maizeGiftToChild / (1- maizeGiftToChild) * this.agedCornStocks[ys]);
			ys--;
		}	
	}
	  
	  
	// COMPLETED
	// Calculate first for each cell the base yield, and then the actual harvest of households. 
	// Update the stocks of corn available in storage. 
	
	public void harvestConsumption(){
						
		if(Math.random() > 0.5)
			this.lastHarvest = this.farmPlot.baseYield * (1 + harvestVariance);
		
		this.agedCornStocks[2] = this.agedCornStocks[1];
		this.agedCornStocks[1] = this.agedCornStocks[0];
		this.agedCornStocks[0] = this.lastHarvest;
		
		this.nutritionNeedRemaining = this.nutritionNeed;
		this.age = this.age++;
		
		int ys = yearsOfStock; 
		while(ys > -1){
			if(this.agedCornStocks[ys] >= nutritionNeedRemaining){
				this.agedCornStocks[ys] = this.agedCornStocks[ys] - this.nutritionNeedRemaining; 
				this.nutritionNeedRemaining = 0; 				
			} else {
				this.nutritionNeedRemaining = this.nutritionNeedRemaining - this.agedCornStocks[ys]; 
				this.agedCornStocks[ys] = 0; 
			}
			ys--; 
		}
		
	}
	
	// COMPLETED 
	public void death(){
		if(this.nutritionNeedRemaining > 0){
			this.farmPlot.ocfarm = 0;
			this.ocHousehold--;
			//FIXME : It has to kill the process.  
		}
		if(this.age > deathAge){
			this.farmPlot.ocfarm = 0;
			this.ocHousehold--;
			//FIXME : It has to kill the process.
		}		  
	}
}

